
/**
 * 计算疫情扩散时间
 * 
 * 题目描述

在一个地图中（地图由 n * n 个区域组成），有部分区域被感染病菌。

感染区域每天都会把周围（上下左右）的4个区域感染。

请根据给定的地图计算，多少天以后，全部区域都会被感染。

如果初始地图上所有区域全部都被感染，或者没有被感染区域，返回 -1

输入描述
一行 N * N 个数字（只包含0，1，不会有其他数字）表示一个地图，数字间用 "," 分割，

0 表示未感染区域
1 表示已经感染区域
每 N 个数字表示地图中一行，输入数据共表示 N 行 N 列的区域地图。例如输入：

1,0,1,0,0,0,1,0,1
表示地图

1,0,1
0,0,0
1,0,1
 

输出描述
一个整数，表示经过多少天以后，全部区域都被感染

备注
1 ≤ N < 200
用例
输入	1,0,1,0,0,0,1,0,1
输出	2
说明	1天以后，地图中仅剩余中心点未被感染；2天以后，全部被感染。
输入	0,0,0,0
输出	-1
说明	无感染区域
输入	1,1,1,1,1,1,1,1,1
输出	-1
说明	全部都感染

 */

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

/**
  * 
  题目解析
自测用例：0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0

一共8x8个区域，感染区传播过程如下图所示：



通过图示我们可以看出，上面病菌扩散过程，起始就是多源BFS的按层扩散过程。

关于广度优先搜索和先进先出的队列结构：

创建一个队列queue，初始时，遍历矩阵，找到所有感染区位置，并加入队列。

queue初始化完成后，我们对queue进行出队操作，每一个出队元素就是一个感染区位置，我们需要将其上下左右的区域全部改为感染区，并将新的感染区入队queue。

这样的话才能保证  第一批感染区  的传播才能优先进行，达到广度优先搜索的目的。



我们再举一个例子，如果采用stack栈来保存感染区的话，则必然先弹栈一个感染区位置，然后将其上下左右区域感染，这个过程中，将新的感染区压栈，而之后再次弹栈，必然是第二批的感染区位置，也就是后进先出，这将会产生深度优先搜索的效果。

最终会产生如下效果



关于BFS按层扩散的实现：



第一个表格中，我们已知了一批感染区，我们将这批感染区位置全部加入queue。

此时我们就知道第一层（第一天）的感染区数量 size = queue.size

当第一层感染区（queue出队）扩散时，会产生（queue入队）第二层感染区位置，此时随着出队入队的动作，queue的size在不断变化。但是我们初始记录的 size 不会改变。

因此，queue出队的前 size 个位置都是第一层的感染区位置。后面入队的第二层感染区位置不会出现在前 size 个。

这样利用 size 变量，我们即可将queue中元素进行分层。

关于BFS的易错点：

BFS最容易出错的逻辑就是：在何时标记某个位置已被感染（扩散过）。

错误的：在出队后标记
正确的：在入队前标记
为什么在出队后标记会有问题呢？

因为，当某个位置pos被扩散后，我们就会将其加入queue，但是如果我们不标记pos被扩散过，则在其入队后，还是有可能被其他点再次扩散到，导致pos被重复入queue。

但是，如果我们在pos被扩散后，入队前，就进行标记，那么后续其他点扩散到pos时，检查其标记，发现被扩散过了，则不会再将其加入queue。

  */
public class 计算疫情扩散时间 {
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] nums = Arrays.stream(sc.nextLine().split(",")).mapToInt(Integer::parseInt).toArray();
        System.out.println(getResult(nums));
    }
 
    public static int getResult(int[] nums) {
        // 题目说会输入n*n个值
        int n = (int) Math.sqrt(nums.length);
 
        // 将一维arr输入转为二维矩阵matrix
        int[][] matrix = new int[n][n];
 
        // 将矩阵中所有感染区域位置记录到queue中,这里选择queue先进先出的原因是保证当天的感染区域并发扩散
        LinkedList<Integer> queue = new LinkedList<>();
 
        // 还原矩阵, 并记录感染区位置到queue中
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                matrix[i][j] = nums[i * n + j];
 
                if (matrix[i][j] == 1) {
                    queue.add(i * n + j);
                }
            }
        }
 
        // 全是感染区，或全是健康区
        if (queue.isEmpty() || queue.size() == nums.length) {
            return -1;
        }
 
        // 健康区个数
        int healthy = nums.length - queue.size();
 
        // 上下左右偏移量
        int[][] offsets = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
 
        // day用于统计感染全部花费的时间
        int day = 0;
 
        while (!queue.isEmpty() && healthy > 0) { // 如果健康区个数为0，说明感染完了
            // size 表示当前层感染区数量
            int size = queue.size();
 
            // 遍历当前层的感染区位置, 并进行扩散
            for (int i = 0; i < size; i++) {
                int pos = queue.removeFirst();
 
                // 当前层感染区位置
                int x = pos / n;
                int y = pos % n;
 
                // 四个方向进行扩散
                for (int[] offset : offsets) {
                    int newX = x + offset[0];
                    int newY = y + offset[1];
 
                    if (newX >= 0 && newX < n && newY >= 0 && newY < n && matrix[newX][newY] == 0) {
                        // 标记(newX, newY)已被感染
                        matrix[newX][newY] = 1; // 在入队前标记
                        // 健康区数量-1
                        healthy--;
                        // 新增感染区属于新层
                        queue.add(newX * n + newY);
                    }
                }
            }
 
            // 一层遍历完就是一天
            day++;
        }
 
        return day;
    }
    
}
